/* eslint-disable @typescript-eslint/no-explicit-any */

import '@/mxgraph'
import '../shape/BaseShape'
import { Graph as mGraph, PropsBarState } from '../types'
import { mxCell as mCell, mxCellState as mCellState, mxEdgeStyle, mxStylesheet } from 'mxgraph'

interface IConifg {
  graph: mGraph
  callback: Function
}

export default {
  /**
   * 加载stencils文件
   * @param stencils
   */
  loadStencils(stencils: { [name: string]: string }) {
    stencils &&
      Object.keys(stencils).forEach((name: string) => {
        const parser = new DOMParser() //eslint-disable-line
        const xmlDoc = parser.parseFromString(stencils[name], 'text/xml') // important to use "text/xml"
        const root = xmlDoc.firstChild
        const prefix = root.getAttribute('name')
        let shape = root.firstChild

        while (shape != null) {
          if (shape.nodeType === mxConstants.NODETYPE_ELEMENT) {
            //@ts-ignore
            const n1 = shape.getAttribute('name')
            const name = prefix ? `${prefix}.${n1}` : n1
            //eslint-disable-line
            //eslint-disable-line
            mxStencilRegistry.addStencil(
              name,
              new mxStencil(shape) //eslint-disable-line
            ) //eslint-disable-line
          }

          shape = shape.nextSibling
        }
      })
  },

  undoListener(config) {
    const { graph, callback } = config

    // Undo/redo
    const undoManager = new mxUndoManager() //eslint-disable-line

    graph.undoManager = undoManager

    const listener = (sender, evt) => {
      undoManager.undoableEditHappened(evt.getProperty('edit'))
    }
    graph.getModel().addListener(mxEvent.UNDO, listener) //eslint-disable-line
    graph.getView().addListener(mxEvent.UNDO, listener) //eslint-disable-line

    this.undoListenerFunc2 = this.undoListenerFunc.bind(this, undoManager, callback)

    document.body.addEventListener('keydown', this.undoListenerFunc2)
  },

  undoListenerFunc(undoManager, callback, e) {
    if (e.target !== e.currentTarget) {
      return false
    }

    const evtobj = window.event ? window.event : e

    if (evtobj.keyCode === 90 && (evtobj.ctrlKey || evtobj.metaKey)) {
      undoManager.undo()

      const { history: histories } = undoManager

      callback && callback(histories)

      // undoManager.redo();
    }
  },

  copyListener(config) {
    const { graph, callback } = config

    this.copyListenerFunc2 = this.copyListenerFunc.bind(this, graph, callback)
    document.body.addEventListener('keydown', this.copyListenerFunc2)
  },

  copyListenerFunc(graph, callback, e) {
    if (e.target !== e.currentTarget) {
      return false
    }

    const evtobj = window.event ? window.event : e
    // command + c / ctrl + c
    if (evtobj.keyCode === 67 && (evtobj.ctrlKey || evtobj.metaKey)) {
      mxClipboard.copy(graph) //eslint-disable-line
    } else if (evtobj.keyCode === 86 && (evtobj.ctrlKey || evtobj.metaKey)) {
      // command + v / ctrl + v
      // cells 复制元素的集合
      const cells = mxClipboard.paste(graph) // eslint-disable-line

      callback && callback(cells)
    }
  },

  deleteListener(config) {
    const { graph, callback } = config

    const { removeCells } = graph
    graph.removeCells = function (cells) {
      const result = removeCells.apply(this, [cells])
      callback && callback(cells)
      return result
    }

    this.deleteListenerFunc2 = this.deleteListenerFunc.bind(this, graph)

    document.body.addEventListener('keydown', this.deleteListenerFunc2)
  },

  deleteListenerFunc(graph, e) {
    if (!(e.target === e.currentTarget || graph.container.contains(e.target))) {
      return false
    }

    const { editingCell } = graph.cellEditor

    const evtobj = window.event ? window.event : e
    if (evtobj.keyCode === 46 || evtobj.keyCode === 8) {
      // 没有cell当前在编辑状态时才可删除
      if (!editingCell) {
        const cellsSelected = graph.getSelectionCells()
        // cellsSelected && cellsSelected.length && graph.removeCells(cellsSelected);

        const cellsSelectable = []
        cellsSelected &&
          cellsSelected.forEach((cell) => {
            if (!cell.disabled) {
              cellsSelectable.push(cell)
            }
          })

        cellsSelectable && cellsSelectable.length && graph.removeCells(cellsSelectable)
      }
    }
  },

  connectorHandler(config) {
    const { graph } = config

    graph.setConnectable(true)
    mxGraphHandler.prototype.guidesEnabled = true //eslint-disable-line

    // Disables automatic handling of ports. This disables the reset of the
    // respective style in mxGraph.cellConnected. Note that this feature may
    // be useful if floating and fixed connections are combined.
    graph.setPortsEnabled(false)

    // Disables floating connections (only connections via ports allowed)
    // graph.connectionHandler.isConnectableCell = function (cell) {
    //   //eslint-disable-line
    //   //eslint-disable-line
    //   return false
    // }

    // edge animation
    // const selectCells = mxConnectionHandler.prototype.selectCells;

    // graph.connectionHandler.selectCells = function (edge, target) {
    //   var state = this.graph.view.getState(edge);

    //   state.shape.node.getElementsByTagName('path')[0].removeAttribute('visibility');
    //   state.shape.node.getElementsByTagName('path')[0].setAttribute('stroke-width', '6');
    //   state.shape.node.getElementsByTagName('path')[0].setAttribute('stroke', 'lightGray');
    //   state.shape.node.getElementsByTagName('path')[1].setAttribute('class', 'flow');

    //   return selectCells.apply(this, arguments);
    // }

    mxEdgeHandler.prototype.isConnectableCell = function (cell) {
      //eslint-disable-line
      //eslint-disable-line
      return graph.connectionHandler.isConnectableCell(cell)
    }

    // Overridden to define per-shape connection points
    mxGraph.prototype.getAllConnectionConstraints = function (
      //eslint-disable-line
      terminal,
      source //eslint-disable-line
    ) {
      //eslint-disable-line
      // if (terminal && terminal.shape && terminal.shape.constraints) {
      //   return terminal.shape.constraints;
      // }

      // return null;

      const { cell } = terminal
      const { pointType } = cell

      if (cell.disabled) {
        return []
      }

      let points = []

      switch (pointType) {
        case 'top':
          points = [
            new mxConnectionConstraint(new mxPoint(0.5, 0), true) //eslint-disable-line
          ]
          break
        case 'left':
          points = [
            new mxConnectionConstraint(new mxPoint(0, 0.5), true) //eslint-disable-line
          ]
          break
        case 'right':
          points = [
            new mxConnectionConstraint(new mxPoint(1, 0.5), true) //eslint-disable-line
          ]
          break
        case 'bottom':
          points = [
            new mxConnectionConstraint(new mxPoint(0.5, 1), true) //eslint-disable-line
          ]
          break
        case 'none':
          points = []
          break
        default:
          points = [
            new mxConnectionConstraint(new mxPoint(0.25, 0), true), //eslint-disable-line
            new mxConnectionConstraint(new mxPoint(0.5, 0), true), //eslint-disable-line
            new mxConnectionConstraint(new mxPoint(0.75, 0), true), //eslint-disable-line
            new mxConnectionConstraint(new mxPoint(0, 0.25), true), //eslint-disable-line
            new mxConnectionConstraint(new mxPoint(0, 0.5), true), //eslint-disable-line
            new mxConnectionConstraint(new mxPoint(0, 0.75), true), //eslint-disable-line
            new mxConnectionConstraint(new mxPoint(1, 0.25), true), //eslint-disable-line
            new mxConnectionConstraint(new mxPoint(1, 0.5), true), //eslint-disable-line
            new mxConnectionConstraint(new mxPoint(1, 0.75), true), //eslint-disable-line
            new mxConnectionConstraint(new mxPoint(0.25, 1), true), //eslint-disable-line
            new mxConnectionConstraint(new mxPoint(0.5, 1), true), //eslint-disable-line
            new mxConnectionConstraint(new mxPoint(0.75, 1), true) //eslint-disable-line
          ]
          break
      }

      return points
    }

    // Defines the default constraints for all shapes
    mxShape.prototype.constraints = [
      //eslint-disable-line
      //eslint-disable-line
      // new mxConnectionConstraint(new mxPoint(0.25, 0), true),
      new mxConnectionConstraint(new mxPoint(0.5, 0), true), //eslint-disable-line
      // new mxConnectionConstraint(new mxPoint(0.75, 0), true),
      // new mxConnectionConstraint(new mxPoint(0, 0.25), true),
      new mxConnectionConstraint(new mxPoint(0, 0.5), true), //eslint-disable-line
      // new mxConnectionConstraint(new mxPoint(0, 0.75), true),
      // new mxConnectionConstraint(new mxPoint(1, 0.25), true),
      new mxConnectionConstraint(new mxPoint(1, 0.5), true), //eslint-disable-line
      // new mxConnectionConstraint(new mxPoint(1, 0.75), true),
      // new mxConnectionConstraint(new mxPoint(0.25, 1), true),
      new mxConnectionConstraint(new mxPoint(0.5, 1), true) //eslint-disable-line
      // new mxConnectionConstraint(new mxPoint(0.75, 1), true)
    ]

    // Edges have no connection points
    mxPolyline.prototype.constraints = null //eslint-disable-line

    // Enables connect preview for the default edge style
    graph.connectionHandler.createEdgeState = () => {
      const edge = graph.createEdge(null, null, null, null, null)

      return new mxCellState(graph.view, edge, graph.getCellStyle(edge)) //eslint-disable-line
    }

    // Changes the default style for edges "in-place" and assigns
    // an alternate edge style which is applied in mxGraph.flip
    // when the user double clicks on the adjustment control point
    // of the edge. The ElbowConnector edge style switches to TopToBottom
    // if the horizontal style is true.
    // const style = graph.getStylesheet().getDefaultEdgeStyle()
    // style[mxConstants.STYLE_ROUNDED] = true //eslint-disable-line
    // style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector; // 备选：orthogonalEdgeStyle
    // style[mxConstants.STYLE_EDGE] = 'orthogonalEdgeStyle' //eslint-disable-line
    // style[mxConstants.STYLE_STROKEWIDTH] = 1;

    // graph.alternateEdgeStyle = 'elbow=vertical';

    // Snaps to fixed points
    mxConstraintHandler.prototype.intersects = function (
      //eslint-disable-line
      icon,
      point,
      source,
      existingEdge
    ) {
      //eslint-disable-line
      return !source || existingEdge || mxUtils.intersects(icon.bounds, point) //eslint-disable-line
    }
  },

  // init VertexToolHandler
  initVertexToolHandler(config) {
    const { graph } = config

    // Defines a subclass for mxVertexHandler that adds a set of clickable
    // icons to every selected vertex.
    function mxVertexToolHandler(state) {
      //eslint-disable-line
      //eslint-disable-line
      mxVertexHandler.apply(this, arguments) //eslint-disable-line
    }

    mxVertexToolHandler.prototype = new mxVertexHandler() //eslint-disable-line
    mxVertexToolHandler.prototype.constructor = mxVertexToolHandler

    mxVertexToolHandler.prototype.domNode = null

    mxVertexToolHandler.prototype.init = () => {
      mxVertexHandler.prototype.init.apply(this, arguments) //eslint-disable-line

      // In this example we force the use of DIVs for images in IE. This
      // handles transparency in PNG images properly in IE and fixes the
      // problem that IE routes all mouse events for a gesture via the
      // initial IMG node, which means the target vertices
      this.domNode = document.createElement('div')
      this.domNode.style.position = 'absolute'
      this.domNode.style.whiteSpace = 'nowrap'

      // Workaround for event redirection via image tag in quirks and IE8
      function createImage(src) {
        if (mxClient.IS_IE && !mxClient.IS_SVG) {
          //eslint-disable-line
          //eslint-disable-line
          const img = document.createElement('div')
          img.style.backgroundImage = `url(${src})`
          img.style.backgroundPosition = 'center'
          img.style.backgroundRepeat = 'no-repeat'
          img.style.display = mxClient.IS_QUIRKS ? 'inline' : 'inline-block' //eslint-disable-line

          return img
        }
        return mxUtils.createImage(src) //eslint-disable-line
      }

      // Delete
      let img = createImage('https://img.alicdn.com/tfs/TB1Z.ETXbvpK1RjSZPiXXbmwXXa-22-22.png') //eslint-disable-line
      img.setAttribute('title', 'Delete')
      img.style.cursor = 'pointer'
      img.style.width = '16px'
      img.style.height = '16px'
      mxEvent.addGestureListeners(
        //eslint-disable-line
        img, //eslint-disable-line
        mxUtils.bind(this, (evt) => {
          //eslint-disable-line
          //eslint-disable-line
          // Disables dragging the image
          mxEvent.consume(evt) //eslint-disable-line
        })
      )
      mxEvent.addListener(
        //eslint-disable-line
        img,
        'click', //eslint-disable-line
        mxUtils.bind(this, function (evt) {
          //eslint-disable-line
          //eslint-disable-line
          this.graph.removeCells([this.state.cell])
          mxEvent.consume(evt) //eslint-disable-line
        })
      )
      this.domNode.appendChild(img)

      // // Size
      // var img = createImage('https://img.alicdn.com/tfs/TB1aucUXhTpK1RjSZR0XXbEwXXa-22-22.png');
      // img.setAttribute('title', 'Resize');
      // img.style.cursor = 'se-resize';
      // img.style.width = '16px';
      // img.style.height = '16px';
      // mxEvent.addGestureListeners(img,
      //   mxUtils.bind(this, function (evt) {
      //     this.start(mxEvent.getClientX(evt), mxEvent.getClientY(evt), 7);
      //     this.graph.isMouseDown = true;
      //     this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
      //     mxEvent.consume(evt);
      //   })
      // );
      // this.domNode.appendChild(img);

      // Move
      img = createImage('https://img.alicdn.com/tfs/TB1inERXmrqK1RjSZK9XXXyypXa-22-22.png') //eslint-disable-line
      img.setAttribute('title', 'Move')
      img.style.cursor = 'move'
      img.style.width = '16px'
      img.style.height = '16px'
      mxEvent.addGestureListeners(
        //eslint-disable-line
        img, //eslint-disable-line
        mxUtils.bind(this, function (evt) {
          //eslint-disable-line
          //eslint-disable-line
          this.graph.graphHandler.start(
            this.state.cell,
            mxEvent.getClientX(evt), //eslint-disable-line
            mxEvent.getClientY(evt) //eslint-disable-line
          ) //eslint-disable-line
          this.graph.graphHandler.cellWasClicked = true
          this.graph.isMouseDown = true
          this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt) //eslint-disable-line
          mxEvent.consume(evt) //eslint-disable-line
        })
      )
      this.domNode.appendChild(img)

      // Connect
      img = createImage('https://img.alicdn.com/tfs/TB1xDQSXgHqK1RjSZFkXXX.WFXa-22-22.png') //eslint-disable-line
      img.setAttribute('title', 'Connect')
      img.style.cursor = 'pointer'
      img.style.width = '16px'
      img.style.height = '16px'
      mxEvent.addGestureListeners(
        //eslint-disable-line
        img, //eslint-disable-line
        mxUtils.bind(this, function (evt) {
          //eslint-disable-line
          //eslint-disable-line
          const pt = mxUtils.convertPoint(
            //eslint-disable-line
            this.graph.container, //eslint-disable-line
            mxEvent.getClientX(evt), //eslint-disable-line
            mxEvent.getClientY(evt) //eslint-disable-line
          ) //eslint-disable-line
          this.graph.connectionHandler.start(this.state, pt.x, pt.y)
          this.graph.isMouseDown = true
          this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt) //eslint-disable-line
          mxEvent.consume(evt) //eslint-disable-line
        })
      )
      this.domNode.appendChild(img)

      this.graph.container.appendChild(this.domNode)
      this.redrawTools()
    }

    mxVertexToolHandler.prototype.redraw = () => {
      mxVertexHandler.prototype.redraw.apply(this) //eslint-disable-line
      this.redrawTools()
    }

    mxVertexToolHandler.prototype.redrawTools = () => {
      if (this.state != null && this.domNode != null) {
        const dy =
          mxClient.IS_VML && document.compatMode === 'CSS1Compat' //eslint-disable-line
            ? 20
            : 4 //eslint-disable-line
        this.domNode.style.left = `${this.state.x + this.state.width - 56}px`
        this.domNode.style.top = `${this.state.y - dy - 26}px`
      }
    }

    mxVertexToolHandler.prototype.destroy = function (sender, me) {
      //eslint-disable-line
      //eslint-disable-line
      mxVertexHandler.prototype.destroy.apply(this, arguments) //eslint-disable-line

      if (this.domNode != null) {
        this.domNode.parentNode.removeChild(this.domNode)
        this.domNode = null
      }
    }

    graph.connectionHandler.createTarget = true

    graph.createHandler = (state) => {
      if (state != null && this.model.isVertex(state.cell)) {
        return new mxVertexToolHandler(state) //eslint-disable-line
      }

      return mxGraph.prototype.createHandler.apply(this, arguments) //eslint-disable-line
    }
  },

  handleDoubleClick(config) {
    const { graph, callback } = config

    // Installs a handler for double click events in the graph
    // that shows an alert box
    graph.addListener(mxEvent.DOUBLE_CLICK, (sender, evt) => {
      //eslint-disable-line
      //eslint-disable-line
      const cell = evt.getProperty('cell')

      callback && callback(cell)

      // evt.consume();
    })
  },

  handleClick(config) {
    const { graph, callback } = config

    graph.addListener(mxEvent.CLICK, (sender, evt) => {
      //eslint-disable-line
      //eslint-disable-line
      const cell = evt.getProperty('cell')
      callback && callback(cell, evt)
    })
  },

  handleChange(config: IConifg) {
    const { graph, callback } = config

    graph.getSelectionModel().addListener(mxEvent.CHANGE, (sender, evt) => {
      //eslint-disable-line
      //eslint-disable-line
      // console.log('------------ getSelectionModel change')
      callback && callback(sender.cells)
    })
  },

  handleHover(config: IConifg) {
    const { graph, callback } = config
  },

  htmlLable(config) {
    const { graph } = config

    // Enables HTML labels
    graph.setHtmlLabels(true)

    // Creates a user object that stores the state
    const doc = mxUtils.createXmlDocument() //eslint-disable-line
    const obj = doc.createElement('UserObject')
    obj.setAttribute('label', 'Hello, World!')
    obj.setAttribute('checked', 'false')
  },

  initAutoSave(config) {
    const { graph, callback } = config

    const mgr = new mxAutoSaveManager(graph) //eslint-disable-line
    mgr.autoSaveDelay = 0 // 自动保存延迟时间设为0
    mgr.save = () => {
      const xml = this.getGraphXml({
        graph
      })

      const formatedNode = this.formatXmlNode(xml)

      if (!formatedNode) {
        return false
      }

      const xmlStr = new XMLSerializer().serializeToString(formatedNode) //eslint-disable-line

      graph.xmlStr = xmlStr

      callback && callback(xmlStr)
    }
  },

  // check the xmlnode format to avoid error
  formatXmlNode(xmlNode) {
    const rootEle = xmlNode && xmlNode.firstElementChild

    let hasRoot = false
    if (rootEle && rootEle.tagName === 'root') {
      hasRoot = true
    }

    let hasIdO = false
    if (rootEle && rootEle.firstElementChild && rootEle.firstElementChild.id === '0') {
      hasIdO = true
    }

    if (!(hasRoot && hasIdO)) {
      console.warn('xmlNode must have root node')
      return false
    }

    const elements = rootEle.children

    const idsArr = []

    elements &&
      Array.from(elements).forEach((element) => {
        const cellId = element && element.getAttribute('id')

        if (idsArr.indexOf(cellId) === -1) {
          idsArr.push(cellId)
        } else {
          console.warn('cell id is duplicated, delete the needless one', element)
          rootEle.removeChild(element)
        }

        if (
          element &&
          element.getAttribute('vertex') === '1' &&
          element.getAttribute('edge') === '1'
        ) {
          console.warn(
            "cell's property vertex and edge cannot both be 1, set property edge to 0",
            element
          )
          element.setAttribute('edge', 0)
        }
      })

    return xmlNode
  },

  /**
   * Returns the XML node that represents the current diagram.
   */
  getGraphXml(config: any) {
    let { ignoreSelection } = config
    const { graph } = config

    ignoreSelection = ignoreSelection != null ? ignoreSelection : true
    let node = null

    if (ignoreSelection) {
      const enc = new mxCodec(mxUtils.createXmlDocument()) //eslint-disable-line
      node = enc.encode(graph.getModel())
    } else {
      node = graph.encodeCells(
        mxUtils.sortCells(
          //eslint-disable-line
          graph.model.getTopmostCells(
            //eslint-disable-line
            graph.getSelectionCells()
          )
        )
      )
    }

    if (graph.view.translate.x !== 0 || graph.view.translate.y !== 0) {
      node.setAttribute('dx', Math.round(graph.view.translate.x * 100) / 100)
      node.setAttribute('dy', Math.round(graph.view.translate.y * 100) / 100)
    }

    node.setAttribute('grid', graph.isGridEnabled() ? '1' : '0')
    node.setAttribute('gridSize', graph.gridSize)
    node.setAttribute('guides', graph.graphHandler.guidesEnabled ? '1' : '0')
    node.setAttribute('tooltips', graph.tooltipHandler.isEnabled() ? '1' : '0')
    node.setAttribute('connect', graph.connectionHandler.isEnabled() ? '1' : '0')
    node.setAttribute('arrows', graph.connectionArrowsEnabled ? '1' : '0')
    node.setAttribute('fold', graph.foldingEnabled ? '1' : '0')
    node.setAttribute('page', graph.pageVisible ? '1' : '0')
    node.setAttribute('pageScale', graph.pageScale)
    node.setAttribute('pageWidth', graph.pageFormat.width)
    node.setAttribute('pageHeight', graph.pageFormat.height)

    if (graph.background != null) {
      node.setAttribute('background', graph.background)
    }

    return node
  },

  /**
   * 自定义锚点
   * @param config
   */
  initCustomPort(config: any) {
    const { pic } = config
    // Replaces the port image
    mxConstraintHandler.prototype.pointImage = new mxImage(pic, 10, 10) //eslint-disable-line
  },

  /**
   * 初始化缩放配置
   */
  initZoomConfig(config: any) {
    const { graph } = config
    graph.keepSelectionVisibleOnZoom = true
    graph.centerZoom = true
  },

  // 缩放
  zoom(config: any) {
    const { graph, type } = config

    switch (type) {
      case 'in':
        graph.zoomIn()
        break
      case 'out':
        graph.zoomOut()
        break
      case 'actual':
        graph.zoomActual()
        break
      default:
        break
    }

    const cellsSelected = graph.getSelectionCells()
    graph.scrollCellToVisible(cellsSelected, true)
    graph.refresh()
  },

  updateStyle(graph, cell, key, value) {
    const model = graph.getModel()

    model.beginUpdate()

    let newStyle = model.getStyle(cell)

    newStyle = mxUtils.setStyle(newStyle, key, value) //eslint-disable-line

    model.setStyle(cell, newStyle)

    model.endUpdate()
  },

  /**
   * 节点重命名监听器
   */
  vertexRenameListener({ callback }) {
    mxCell.prototype.valueChangedCallback = callback //eslint-disable-line

    // 若没重写过 valueChanged 方法则重写
    if (!mxCell.prototype.hasRewriteValueChanged) {
      //eslint-disable-line
      //eslint-disable-line
      mxCell.prototype.hasRewriteValueChanged = true //eslint-disable-line

      const { valueChanged } = mxCell.prototype //eslint-disable-line
      mxCell.prototype.valueChanged = function (newValue) {
        //eslint-disable-line
        //eslint-disable-line
        const { valueChangedCallback } = mxCell.prototype //eslint-disable-line

        valueChangedCallback && valueChangedCallback(this, newValue)
        valueChanged.apply(this, [newValue])
      }
    }
  },

  /**
   * 重命名一个cell
   * @param {*} newName 新名字（labelName）
   * @param {*} cell cell
   * @param {*} graph cell 归属的 graph
   */
  renameCell(newName, cell, graph) {
    cell.value = newName
    graph.refresh() // 重新渲染graph
  },

  /**
   * remove event listeners
   */
  removeEventListeners() {
    document.body.removeEventListener('keydown', this.undoListenerFunc2)
    document.body.removeEventListener('keydown', this.copyListenerFunc2)
    document.body.removeEventListener('keydown', this.deleteListenerFunc2)
  },

  startPanning(graph) {
    // graph.setPanning(true);
    // Enables panning with left mouse button
    graph.panningHandler.useLeftButtonForPanning = true
    graph.panningHandler.ignoreCell = true
    graph.container.style.cursor = 'move'
  },

  stopPanning(graph) {
    graph.panningHandler.useLeftButtonForPanning = false
    graph.panningHandler.ignoreCell = false
    graph.container.style.cursor = 'auto'
  },

  findItemFromArray(arr, query) {
    const key = Object.keys(query)[0]
    const value = query[key]

    let result

    arr &&
      arr.forEach((item) => {
        if (item && item[key] === value) {
          result = item
        }
      })

    return result
  },

  /**
   * 读取Cell的状态
   * @param cell
   */
  getSelectionStateForCell(graph: Graph, cell: mCell): PropsBarState {
    const result: PropsBarState = this.createSelectionStateForCell()
    if (graph.getModel().isVertex(cell)) {
      result.resizable = result.resizable && graph.isCellResizable(cell)
      result.rotatable = result.rotatable && graph.isCellRotatable(cell)
      result.movable =
        result.movable &&
        graph.isCellMovable(cell) &&
        !graph.isTableRow(cell) &&
        !graph.isTableCell(cell)
      result.table = result.table || graph.isTable(cell)
      result.cell = result.cell || graph.isTableCell(cell)
      result.row = result.row || graph.isTableRow(cell)
      result.vertices.push(cell)
      const geo = graph.getCellGeometry(cell)

      if (geo != null) {
        if (geo.width > 0) {
          if (result.width == null) {
            result.width = geo.width
          } else if (result.width != geo.width) {
            result.width = ''
          }
        } else {
          result.containsLabel = true
        }

        if (geo.height > 0) {
          if (result.height == null) {
            result.height = geo.height
          } else if (result.height != geo.height) {
            result.height = ''
          }
        } else {
          result.containsLabel = true
        }

        if (!geo.relative || geo.offset != null) {
          const x = geo.relative ? geo.offset.x : geo.x
          const y = geo.relative ? geo.offset.y : geo.y

          if (result.x == null) {
            result.x = x
          } else if (result.x != x) {
            result.x = ''
          }

          if (result.y == null) {
            result.y = y
          } else if (result.y != y) {
            result.y = ''
          }
        }
      }
    } else if (graph.getModel().isEdge(cell)) {
      result.edges.push(cell)
      result.resizable = false
      result.rotatable = false
      result.movable = false
    }

    const state = graph.view.getState(cell)

    if (state != null) {
      result.autoSize = result.autoSize || this.isAutoSizeState(state)
      result.glass = result.glass && this.isGlassState(state)
      result.rounded = result.rounded && this.isRoundedState(state)
      result.lineJumps = result.lineJumps && this.isLineJumpState(state)
      result.image = result.image && this.isImageState(state)
      result.shadow = result.shadow && this.isShadowState(state)
      result.fill = result.fill && this.isFillState(state)
      result.stroke = result.stroke && this.isStrokeState(state)

      const shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null)
      result.containsImage = result.containsImage || shape == 'image'

      for (const key in state.style) {
        const value = state.style[key]

        if (value != null) {
          if (result.style[key] == null) {
            result.style[key] = value
          } else if (result.style[key] != value) {
            result.style[key] = ''
          }
        }
      }
    }
    return result
  },

  /**
   * 新的Cell State
   * @returns
   */
  createSelectionStateForCell(): PropsBarState {
    const result: PropsBarState = {
      vertices: [],
      edges: [],
      x: null,
      y: null,
      width: null,
      height: null,
      style: {},
      containsImage: false,
      containsLabel: false,
      fill: true,
      glass: true,
      rounded: true,
      autoSize: false,
      image: true,
      shadow: true,
      lineJumps: true,
      resizable: true,
      table: false,
      cell: false,
      row: false,
      movable: true,
      rotatable: true,
      stroke: true
    }
    return result
  },

  isAutoSizeState(state: mCellState) {
    return mxUtils.getValue(state.style, mxConstants.STYLE_AUTOSIZE, null) == '1'
  },

  isGlassState(state: mCellState) {
    const shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null)

    return (
      shape == 'label' ||
      shape == 'rectangle' ||
      shape == 'internalStorage' ||
      shape == 'ext' ||
      shape == 'umlLifeline' ||
      shape == 'swimlane' ||
      shape == 'process'
    )
  },

  isRoundedState(state: mCellState): boolean {
    const roundableShapes = [
      'label',
      'rectangle',
      'internalStorage',
      'corner',
      'parallelogram',
      'swimlane',
      'triangle',
      'trapezoid',
      'ext',
      'step',
      'tee',
      'process',
      'link',
      'rhombus',
      'offPageConnector',
      'loopLimit',
      'hexagon',
      'manualInput',
      'card',
      'curlyBracket',
      'singleArrow',
      'callout',
      'doubleArrow',
      'flexArrow',
      'umlLifeline'
    ]
    return state.shape != null
      ? state.shape.isRoundable()
      : mxUtils.indexOf(
          roundableShapes,
          mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null)
        ) >= 0
  },

  isLineJumpState(state: mCellState): boolean {
    const shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null)
    const curved = mxUtils.getValue(state.style, mxConstants.STYLE_CURVED, false)
    return !curved && (shape == 'connector' || shape == 'filledEdge')
  },

  isImageState(state: mCellState): boolean {
    const shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null)
    return shape == 'label' || shape == 'image'
  },

  isShadowState(state: mCellState): boolean {
    const shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null)
    return shape != 'image'
  },

  isFillState(state: mCellState): boolean {
    return (
      !this.isSpecialColor(state.style[mxConstants.STYLE_FILLCOLOR]) &&
      (state.view.graph.model.isVertex(state.cell) ||
        mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) == 'arrow' ||
        mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) == 'filledEdge' ||
        mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) == 'flexArrow')
    )
  },

  isSpecialColor(color: string): boolean {
    return (
      mxUtils.indexOf(
        [
          mxConstants.STYLE_STROKECOLOR,
          mxConstants.STYLE_FILLCOLOR,
          'inherit',
          'swimlane',
          'indicated'
        ],
        color
      ) >= 0
    )
  },

  isStrokeState(state: mCellState): boolean {
    return !this.isSpecialColor(state.style[mxConstants.STYLE_STROKECOLOR])
  },

  /**
   * 为graph设定样式
   */
  setStylesheet(graph: mGraph, nodeStr: string) {
    const node = mxUtils.parseXml(nodeStr).documentElement
    const dec = new mxCodec(node.ownerDocument)
    dec.decode(node, graph.getStylesheet())
  },
  /**
   * 临时生成一个画布
   * @param stylesheet
   * @returns
   */
  createTemporaryGraph(stylesheet?: any) {
    const graph = new Graph(document.createElement('div'))
    if (stylesheet) {
      graph.stylesheet.styles = mxUtils.clone(stylesheet.styles)
    }

    graph.resetViewOnRootChange = false
    graph.setConnectable(false)
    graph.gridEnabled = false
    graph.autoScroll = false
    graph.setTooltips(false)
    graph.setEnabled(false)

    // Container must be in the DOM for correct HTML rendering
    graph.container.style.visibility = 'hidden'
    graph.container.style.position = 'absolute'
    graph.container.style.overflow = 'hidden'
    graph.container.style.height = '1px'
    graph.container.style.width = '1px'

    return graph
  },

  /**
   * 根据根据模板生成一个Vertex cell
   */
  createVertexTemplateEntry(
    style: string,
    width: number = 32,
    height: number = 32,
    value: string = '',
    title: string = '',
    showLabel: boolean = false,
    showTitle: boolean = false,
    allowCellsInserted: boolean = true
  ): mxCell {
    const cell = new mxCell(value != null ? value : '', new mxGeometry(0, 0, width, height), style)

    cell.vertex = true
    return cell
  },

  /**
   * 根据根据模板生成一个Edge cell
   */
  createEdgeTemplateEntry(
    style: string,
    width: number,
    height: number,
    value: string = '',
    title: string = '',
    showLabel: boolean = false,
    showTitle: boolean = false,
    allowCellsInserted: boolean = true
  ): mCell {
    const cell = new mxCell(value != null ? value : '', new mxGeometry(0, 0, width, height), style)
    cell.geometry.setTerminalPoint(new mxPoint(0, height), true)
    cell.geometry.setTerminalPoint(new mxPoint(width, 0), false)
    cell.geometry.relative = true
    cell.edge = true
    return cell
  },

  /**
   * 获取Cell的属性值
   */
  getCellAttr(cell: mCell, key: string): any {
    const v = cell.value
    if (mxUtils.isNode(v, 'object')) {
      const value = v.getAttribute(key)
      return value
    }
    return ''
  }
}
